﻿// 游戏状态
const GameStatus = {
    END: 0,     // 已结束
    PLAYING: 1  // 游戏中
}

/**
 * 游戏类的构造方法。
 * @param drawer    绘图者
 * @param gameBoard 游戏区域
 */
function Game(drawer, gameBoard) {
    const mDrawer = drawer                                      // 绘图者
    const mGameBoard = gameBoard                                // 游戏区域
    const mGameOverEvent = document.createEvent("customEvent")  // 游戏结束事件
    var mNumbers = null                                         // 数字矩阵
    var mStatus = GameStatus.END                                // 状态

    // 初始化成员
    mGameOverEvent.initEvent(Const.UM_GAME_OVER, false, false)
    mDrawer.font = Const.GAME_BOARD_FONT_SIZE.toString() + "px " + Const.GAME_BOARD_FONT_NAME   // 字体
    mDrawer.textAlign = "center"    // 文字居中

    // 重绘游戏区域
    drawGameBoard()

    /**
     * 移动之后要做的事。
     * @param changed   指示移动之后，每个格上的数字是否改变。任意一格有改变则为 true，否则为 false
     */
    function afterMove(changed) {
        // 重绘游戏区域
        drawGameBoard()

        // 画所有数字
        drawAllNumbers()

        // 如果胜利或失败，则游戏结束
        if (isWon()) {
            gameOver(true)
            return
        }
        if (isLost()) {
            gameOver(false)
            return
        }

        // 创建下一个数
        if (changed) {
            createRandomNumber()
        }
    }

    /**
     * 在所有格中，随机找到未被使用的一个，将格上的数字设为2或4。
     */
    function createRandomNumber() {
        var row = Math.floor(Math.random() * 10) % Const.GAME_BOARD_GRID_COUNT_V
        var line = Math.floor(Math.random() * 10) % Const.GAME_BOARD_GRID_COUNT_H

        while (mNumbers[row][line] != 0) {
            row = Math.floor(Math.random() * 10) % Const.GAME_BOARD_GRID_COUNT_V
            line = Math.floor(Math.random() * 10) % Const.GAME_BOARD_GRID_COUNT_H
        }

        mNumbers[row][line] = Math.floor(Math.random() * 10) % 8 <= 3 ? 2: 4
        drawOneNumber(line, row, mNumbers[row][line])
        mGameBoard.repaint()
    }

    /**
     * 画所有数字。
     */
    function drawAllNumbers() {
        for (var i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            for (var j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                if (mNumbers[i][j] != 0) {
                    drawOneNumber(j, i, mNumbers[i][j])
                }
            }
        }

        mGameBoard.repaint()
    }

    /**
     * 画游戏区域。
     */
    function drawGameBoard() {
        // 画边框
        mDrawer.strokeRect(0, 0, Const.GAME_BOARD_WIDTH, Const.GAME_BOARD_HEIGHT)

        // 画背景
        mDrawer.fillStyle = Const.GAME_BOARD_COLOR
        mDrawer.fillRect(1, 1, Const.GAME_BOARD_WIDTH - 1, Const.GAME_BOARD_HEIGHT - 1)

        // 画竖线
        mDrawer.fillStyle = "rgb(0, 0, 0)"
        var i
        for (i = 1; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            mDrawer.fillRect(i * Const.GRID_SIZE, 0, 1, Const.GAME_BOARD_HEIGHT)
        }

        // 画横线
        for (i = 1; i < Const.GAME_BOARD_GRID_COUNT_H; i++) {
            mDrawer.fillRect(0, i * Const.GRID_SIZE, Const.GAME_BOARD_WIDTH, 1)
        }

        // 通知游戏区域重绘
        mGameBoard.repaint()
    }

    /**
     * 画单个数字。
     * @param line      列
     * @param row       行
     * @param number    要画的数字
     */
    function drawOneNumber(line, row, number) {
        // 计算位置
        const x = line * Const.GRID_SIZE + Const.GRID_SIZE / 2
        const y = row * Const.GRID_SIZE + (Const.GRID_SIZE + Const.GAME_BOARD_FONT_SIZE) / 2 - Const.GAME_BOARD_FONT_SIZE / 8

        // 画数字
        const text = number.toString()
        mDrawer.fillText(text, x, y)
    }

    /**
     * 游戏结束。
     * @param isWon 胜利则为 true，否则为 false
     */
    function gameOver(isWon) {
        // 将状态设置为已结束
        mStatus = GameStatus.END

        // 发送通知
        mGameOverEvent.arg1 = isWon
        document.dispatchEvent(mGameOverEvent)
    }

    /**
     * 判断是否失败。
     * @return 失败则返回 true，否则返回 false
     */
    function isLost() {
        var i, j    // 循环变量

        // 横向检查所有数字
        for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            for (j = 0; j < Const.GAME_BOARD_GRID_COUNT_H - 1; j++) {
                if (mNumbers[i][j] == 0 || mNumbers[i][j] == mNumbers[i][j + 1]) {
                    return false
                }
            }
        }

        // 纵向检查所有数字
        for (j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
            for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V - 1; i++) {
                if (mNumbers[i][j] == mNumbers[i + 1][j]) {
                    return false
                }
            }
        }

        // 判断最右边一列是否有零
        for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            if (mNumbers[i][3] == 0) {
                return false
            }
        }

        return true
    }

    /**
     * 判断是否胜利。
     * @return 胜利则返回 true，否则返回 false
     */
    function isWon() {
        // 检查所有数字
        for (var i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            for (var j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                if (mNumbers[i][j] == Const.WIN_NUMBER) {
                    return true
                }
            }
        }

        return false
    }

    /**
     * 下移。
     */
    function moveDown() {
        var i, j, k         // 循环变量
        var ignore = -1     // 忽略的格
        var changed = false // 是否相加、移动

        // 从左到右遍历每一列
        for (j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
            // 每列从上到下遍历每一格，判断是否有相同数，有则相加
            for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
                // 如果该数为零或之前被加过，则什么也不做
                if (mNumbers[i][j] == 0 || i == ignore) {
                    continue
                }

                // 不为零则从该数下方逐个检查
                for (k = i + 1; k < Const.GAME_BOARD_GRID_COUNT_V; k++) {
                    // 遇到零跳过
                    if (mNumbers[k][j] == 0) {
                        continue
                    }

                    // 如果两数不相等，则结束本次循环
                    if (mNumbers[k][j] != mNumbers[i][j]) {
                        break
                    }

                    // 两数相等则相加
                    mNumbers[k][j] += mNumbers[i][j]
                    mNumbers[i][j] = 0
                    ignore = k
                    changed = true
                }
            }

            // 所有数字向下移动
            for (i = Const.GAME_BOARD_GRID_COUNT_V - 2; i >= 0; i--) {
                // 如果该数为零（没有数），则什么也不做
                if (mNumbers[i][j] == 0) {
                    continue
                }

                // 不为零则向下移动
                for (k = i + 1; k < Const.GAME_BOARD_GRID_COUNT_V; k++) {
                    if (mNumbers[k][j] == 0) {
                        mNumbers[k][j] = mNumbers[k - 1][j]
                        mNumbers[k - 1][j] = 0
                        changed = true
                    }
                }
            }
        }

        afterMove(changed)
    }

    /**
     * 左移。
     */
    function moveLeft() {
        var i, j, k         // 循环变量
        var ignore = -1     // 忽略的格
        var changed = false // 是否相加、移动

        // 从上到下遍历每一行
        for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            // 每行从右到左遍历每一格，判断是否有相同数，有则相加
            for (j = Const.GAME_BOARD_GRID_COUNT_H - 1; j > 0; j--) {
                // 如果该数为零或之前被加过，则什么也不做
                if (mNumbers[i][j] == 0 || j == ignore) {
                    continue
                }

                // 不为零则从该数左面逐个检查
                for (k = j - 1; k >= 0; k--) {
                    // 遇到零跳过
                    if (mNumbers[i][k] == 0) {
                        continue
                    }

                    // 如果两数不相等，则结束本次循环
                    if (mNumbers[i][k] != mNumbers[i][j]) {
                        break
                    }

                    // 两数相等则相加
                    mNumbers[i][k] += mNumbers[i][j]
                    mNumbers[i][j] = 0
                    ignore = k
                    changed = true
                }
            }

            // 所有数字向左移动
            for (j = 1; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                // 如果该数为零（没有数），则什么也不做
                if (mNumbers[i][j] == 0) {
                    continue
                }

                // 不为零则向左移动
                for (k = j - 1; k >= 0; k--) {
                    if (mNumbers[i][k] == 0) {
                        mNumbers[i][k] = mNumbers[i][k + 1]
                        mNumbers[i][k + 1] = 0
                        changed = true
                    }
                }
            }
        }

        afterMove(changed)
    }

    /**
     * 右移。
     */
    function moveRight() {
        var i, j, k         // 循环变量
        var ignore = -1     // 忽略的格
        var changed = false // 是否相加、移动

        // 从上到下遍历每一行
        for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            // 每行从左到右遍历每一格，判断是否有相同数，有则相加
            for (j = 0; j < Const.GAME_BOARD_GRID_COUNT_H - 1; j++) {
                // 如果该数为零或之前被加过，则什么也不做
                if (mNumbers[i][j] == 0 || j == ignore) {
                    continue
                }

                // 不为零则从该数右面逐个检查
                for (k = j + 1; k < Const.GAME_BOARD_GRID_COUNT_H; k++) {
                    // 遇到零跳过
                    if (mNumbers[i][k] == 0) {
                        continue
                    }

                    // 如果两数不相等，则结束本次循环
                    if (mNumbers[i][k] != mNumbers[i][j]) {
                        break
                    }

                    // 两数相等则相加
                    mNumbers[i][k] += mNumbers[i][j]
                    mNumbers[i][j] = 0
                    ignore = k
                    changed = true
                }
            }

            // 所有数字向右移动
            for (j = Const.GAME_BOARD_GRID_COUNT_H - 2; j >= 0; j--) {
                // 如果该数为零（没有数），则什么也不做
                if (mNumbers[i][j] == 0) {
                    continue
                }

                // 不为零则向右移动
                for (k = j + 1; k < Const.GAME_BOARD_GRID_COUNT_H; k++) {
                    if (mNumbers[i][k] == 0) {
                        mNumbers[i][k] = mNumbers[i][k - 1]
                        mNumbers[i][k - 1] = 0
                        changed = true
                    }
                }
            }
        }

        afterMove(changed)
    }

    /**
     * 上移。
     */
    function moveUp() {
        var i, j, k         // 循环变量
        var ignore = -1     // 忽略的格
        var changed = false // 是否相加、移动

        // 从左到右遍历每一列
        for (j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
            // 每列从下到上遍历每一格，判断是否有相同数，有则相加
            for (i = Const.GAME_BOARD_GRID_COUNT_V - 1; i > 0; i--) {
                // 如果该数为零或之前被加过，则什么也不做
                if (mNumbers[i][j] == 0 || i == ignore) {
                    continue
                }

                // 不为零则从该数上方逐个检查
                for (k = i - 1; k >= 0; k--) {
                    // 遇到零跳过
                    if (mNumbers[k][j] == 0) {
                        continue
                    }

                    // 如果两数不相等，则结束本次循环
                    if (mNumbers[k][j] != mNumbers[i][j]) {
                        break
                    }

                    // 两数相等则相加
                    mNumbers[k][j] += mNumbers[i][j]
                    mNumbers[i][j] = 0
                    ignore = k
                    changed = true
                }
            }

            // 所有数字向上移动
            for (i = 1; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
                // 如果该数为零（没有数），则什么也不做
                if (mNumbers[i][j] == 0) {
                    continue
                }

                // 不为零则向上移动
                for (k = i - 1; k >= 0; k--) {
                    if (mNumbers[k][j] == 0) {
                        mNumbers[k][j] = mNumbers[k + 1][j]
                        mNumbers[k + 1][j] = 0
                        changed = true
                    }
                }
            }
        }

        afterMove(changed)
    }

    /**
     * 按键事件的响应方法。
     * @param key   按键
     */
    this.onKeyPressed = function(key) {
        if (mStatus != GameStatus.PLAYING) {
            return false
        }

        switch (key) {
            case Const.KEY_DOWN:
                moveDown()
                break
            case Const.KEY_LEFT:
                moveLeft()
                break
            case Const.KEY_RIGHT:
                moveRight()
                break
            case Const.KEY_UP:
                moveUp()
                break
        }

        return true
    }

    /**
     * 游戏开始。
     */
    this.start = function() {
        // 重绘游戏区域
        drawGameBoard()

        // 数字矩阵清零
        mNumbers = new Array(Const.GAME_BOARD_GRID_COUNT_V)
        for (var i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            mNumbers[i] = new Array(Const.GAME_BOARD_GRID_COUNT_H)
            for (var j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                mNumbers[i][j] = 0
            }
        }

        // 创建随机数字
        createRandomNumber()

        // 将状态设置为游戏中
        mStatus = GameStatus.PLAYING
    }
}
